package com.kamistoat.meimeistore.ware.service.impl;

import com.kamistoat.common.To.RabbitMQTo.OrderTo;
import com.kamistoat.common.To.RabbitMQTo.StockLockedTo;
import com.kamistoat.common.exception.ALLRunTimeException;
import com.kamistoat.common.utils.R;
import com.kamistoat.meimeistore.ware.dao.WareOrderTaskDetailDao;
import com.kamistoat.meimeistore.ware.entity.WareOrderTaskDetailEntity;
import com.kamistoat.meimeistore.ware.entity.WareOrderTaskEntity;
import com.kamistoat.meimeistore.ware.feign.OrderFeignService;
import com.kamistoat.meimeistore.ware.feign.ProductFeignService;
import com.kamistoat.meimeistore.ware.service.WareOrderTaskDetailService;
import com.kamistoat.meimeistore.ware.service.WareOrderTaskService;
import com.kamistoat.meimeistore.ware.vo.*;
import com.kamistoat.meimeistore.ware.dao.WareSkuDao;
import com.kamistoat.meimeistore.ware.vo.OrderLockVo;
import com.kamistoat.meimeistore.ware.vo.SkuWareHasStockVo;
import com.kamistoat.meimeistore.ware.vo.WareSkuLockVo;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.kamistoat.common.utils.PageUtils;
import com.kamistoat.common.utils.Query;

import com.kamistoat.meimeistore.ware.entity.WareSkuEntity;
import com.kamistoat.meimeistore.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    ProductFeignService productFeignService;
    @Autowired
    OrderFeignService orderFeignService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    WareOrderTaskService wareOrderTaskService;
    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;
    @Autowired
    WareOrderTaskDetailDao wareOrderTaskDetailDao;
    @Autowired
    WareSkuDao wareSkuDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                new QueryWrapper<WareSkuEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 自己的方法，根据SKUID和仓库Id查出库存
     *
     * @param params 查询条件
     * @return 分页封装
     */
    @Override
    public PageUtils queryPageByWareSkuInfo(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<>();
        if (!StringUtils.isEmpty(params.get("skuId"))) {
            wrapper.eq("sku_id", params.get("skuId"));
        }
        if (!StringUtils.isEmpty(params.get("wareId"))) {
            wrapper.eq("ware_id", params.get("wareId"));
        }
        IPage<WareSkuEntity> page = this.page(new Query<WareSkuEntity>().getPage(params), wrapper);
        return new PageUtils(page);
    }

    /**
     * 为指定商品添加库存
     *
     * @param skuId  商品skuId
     * @param wareId 商品仓库Id
     * @param skuNum 商品库存
     */
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        WareSkuEntity wareSkuEntity = baseMapper.selectOne(new QueryWrapper<WareSkuEntity>().
                eq("sku_id", skuId).eq("ware_id", wareId));
        if (wareSkuEntity != null) {
            wareSkuEntity.setStock(wareSkuEntity.getStock() + skuNum);
            baseMapper.updateById(wareSkuEntity);
        } else {
            // 如果还没有这条库存关系，直接用save
            WareSkuEntity skuEntity = new WareSkuEntity();
            skuEntity.setSkuId(skuId);
            skuEntity.setSkuName(productFeignService.getSkuFeign(skuId).getSkuName());
            skuEntity.setStock(skuNum);
            skuEntity.setWareId(wareId);
            skuEntity.setStockLocked(0);
            this.save(skuEntity);
        }
    }

    /**
     * 远程接口的实现方法，返回sku的是否有库存
     * true有库存，false无库存
     *
     * @param skuIdList skuID列表
     * @return 哈希表
     */
    @Override
    public Map<String, Boolean> skuHasStock(List<Long> skuIdList) {
        Map<String, Boolean> skuStockMap = new HashMap<>();
        for (Long aLong : skuIdList) {
            // 注意，存在一个商品在多个仓库中都有库存的情况，因此要把多个仓库的库存合起来
            List<WareSkuEntity> wareSkuList = this.list(new QueryWrapper<WareSkuEntity>().eq("sku_id", aLong));
            int stock = 0;
            if (wareSkuList != null && wareSkuList.size() > 0) {
                for (WareSkuEntity wareSkuEntity : wareSkuList) {
                    stock = stock + wareSkuEntity.getStock();
                    // 考虑到有人下订单但未支付的锁定库存情况，还要减去锁定的库存
                    stock = stock - wareSkuEntity.getStockLocked();
                }
            }
            skuStockMap.put(aLong.toString(), stock > 0);
        }
        return skuStockMap;
    }

    /**
     * 锁定库存
     *
     * @param wareSkuLockVo 包括订单sn和所有订单小项内容
     * @return
     */
    @Transactional(rollbackFor = ALLRunTimeException.NoStockException.class)
    @Override
    public Boolean orderLockStock(WareSkuLockVo wareSkuLockVo) {
        // 0创建库存-订单工作单，并保存到数据库
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(wareSkuLockVo.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        // 1找到每个商品在哪个仓库都有库存
        // 获取订单中所有商品小项的信息
        List<OrderLockVo> lockList = wareSkuLockVo.getLockList();
        // 查询每个商品都在那几个仓库有货。所有商品的分布情况制作成一个List
        List<SkuWareHasStockVo> skuWareHasStockVoList = lockList.stream().map(orderItemVo -> {
            SkuWareHasStockVo skuWareHasStockVo = new SkuWareHasStockVo();
            // 获取当前商品Id
            Long skuId = orderItemVo.getSkuId();
            skuWareHasStockVo.setSkuId(skuId);
            // 调用自定义的SQL方法，查询当前商品在哪几个仓库中有库存
            List<Long> wareIdList = baseMapper.listWareHasSkuStock(skuId);
            skuWareHasStockVo.setWareId(wareIdList);
            // 设置当前商品希望锁定几件库存
            skuWareHasStockVo.setNum(orderItemVo.getCount());
            return skuWareHasStockVo;
        }).collect(Collectors.toList());


        // 2锁定库存
        Boolean allLock = true;  // 用于标记全部订单小项是否被锁定成功

        // 遍历所有商品分布情况的列表
        for (SkuWareHasStockVo skuWareHasStockVo : skuWareHasStockVoList) {
            Boolean skuLock = false;  // 用于标记当前商品小项是否被锁定成功
            // 对每一件商品的分布情况进行遍历，判断它的哪一个仓库能够扣除掉库存
            // 当然这里偷懒了，这里是找到第一个库存够大的仓库直接就在这里扣减，然后就退出了。真实开发肯定是用一系列算法算出在哪个仓库扣减最优秀
            Long skuId = skuWareHasStockVo.getSkuId();
            List<Long> wareIdList = skuWareHasStockVo.getWareId();
            // 对每一个仓库进行扣库存，哪个扣成功了直接去扣下一个订单小项
            // 这里偷了个懒，只要有一件商品扣库存失败，整个锁定库存的操作都退出。
            if (wareIdList == null || wareIdList.size() == 0) {
                // 如果某一件商品没有库存，那一定是扣库存失败。直接全部退出
                allLock = false;
                // 抛出异常的同时，记录下出现异常的skuId
                throw new ALLRunTimeException.NoStockException(skuId);
            }
            for (Long wareId : wareIdList) {
                // 依次去当前订单小项的仓库中扣
                // 直接用SQL语言来做，成功返回操作1行，失败返回操作0行
                Long count = baseMapper.lockSkuStock(skuId, wareId, skuWareHasStockVo.getNum());
                if (count == 1) {
                    // 当前仓库锁定成功
                    skuLock = true;
                    // 创建锁库存工单并保存到数据库
                    WareOrderTaskDetailEntity wareOrderTaskDetailEntity = new WareOrderTaskDetailEntity();
                    wareOrderTaskDetailEntity.setSkuId(skuWareHasStockVo.getSkuId());  // 当前商品SKu
                    wareOrderTaskDetailEntity.setWareId(wareId);  // 当前在哪个仓库进行锁定
                    wareOrderTaskDetailEntity.setSkuNum(skuWareHasStockVo.getNum());  // 当前锁定了多少件库存
                    wareOrderTaskDetailEntity.setTaskId(wareOrderTaskEntity.getId());  // 锁定工单对应哪一张库存-订单工单
                    wareOrderTaskDetailEntity.setLockStatus(1);
                    wareOrderTaskDetailService.save(wareOrderTaskDetailEntity);
                    // 向RabbitMQ的延迟队列发送消息
                    // 注意，整个锁定库存操作有失败回滚保险，但是这里的Rabbit是不会的
                    // 如果锁定失败，那么数据库创建的工作单/工作单详情/更改的库存 都会恢复原样，唯独传入到Rabbit中的消息依然存在
                    // 但是并不影响。因为Rabbit的作用是未支付订单超时取消，也就是Rabbit中的消息存在的意义就是为了取消支付，恢复库存。
                    // Rabbit通过判断消息的工单id的状态来决定自己要做啥
                    // 如果在最开始，锁定库存时就出错了，那么数据库中压根就没有这份工单Id，Rabbit一看找不到工单Id，就明白了原来是锁定库存就出错了
                    // 那么它不做任何操作即可
                    StockLockedTo stockLockedTo = new StockLockedTo();
                    stockLockedTo.setWareOrderId(wareOrderTaskEntity.getId());  // 向消息中心保存 库存-订单工单 Id
                    stockLockedTo.setStockId(wareOrderTaskDetailEntity.getId());  // 向消息中心保存 锁定工单 id
                    rabbitTemplate.convertAndSend(
                            "stock-event-exchange",
                            "stock.locked",
                            stockLockedTo
                    );
                    break;  // 跳出当前商品小项的锁定，去下一个
                }
                // if执行不成功，就是当前仓库锁定失败，尝试下一个仓库
            }
            // 当前订单小项全部仓库遍历结束后，判断是否锁成功。没成功同样全部退出
            if (!skuLock) {
                allLock = false;
                throw new ALLRunTimeException.NoStockException(skuId);
            }
        }
        // 能走到这，那肯定是所有的商品都锁定成功了，返回一个标志位
        return allLock;
    }

    /**
     * 监听死信队列，当2分钟到了以后主动去执行库存解锁
     *
     * @param stockLockedTo
     */
    @Override
    public void stockLockedRelease(StockLockedTo stockLockedTo) {
        /**
         * 获取到 库存-订单 id，先从数据库查看是否还存在这条工单记录。如果没有则说明库存服务有异常，已经被本地事务所回滚，直接不需要进行任何操作
         * 如果工单还在，那么根据订单sn去数据库看看订单还在不在，如果不在说明订单有异常被回滚了，回滚解锁库存。如果订单还在，看看是否未支付被取消，是的话也回滚解锁库存
         */
        Long wareOrderId = stockLockedTo.getWareOrderId();
        WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getById(wareOrderId);
        if (wareOrderTaskEntity == null) {
            // 如果 库存-订单 工单根本就不存在，说明库存本地事务已经回滚了，不需要做任何事情
            return;
        } else {
            // 如果工单存在，先去看看订单在不在
            String orderSn = wareOrderTaskEntity.getOrderSn();
            R orderBySn = orderFeignService.getOrderBySn(orderSn);
            if ((int) orderBySn.get("code") == 1 || (int) orderBySn.get("status") == 4) {
                // 订单都不存在了，直接执行库存回滚
                // 订单存在，但是被超时关闭或无效，也执行库存回滚
                // 或运算只要第一个成功后面的条件直接就不看了，不会出错
                Long stockId = stockLockedTo.getStockId();
                WareOrderTaskDetailEntity lockDetail = wareOrderTaskDetailService.getById(stockId);  // 只要到了这一步，一定存在的，当然再判断一下也可以
                if (lockDetail != null && lockDetail.getLockStatus() == 1) {
                    // 如果工单状态是锁定，解锁库存
                    // 注意这里很重要，一定要判断工单是否是锁定。因为之后会有主动被动两种机制互相补充解锁，这里必须进行判断防止重复解锁
                    baseMapper.stockLockedRelease(lockDetail.getSkuId(), lockDetail.getWareId(), lockDetail.getSkuNum());
                    // 更改工单状态后保存
                    lockDetail.setLockStatus(2);
                    wareOrderTaskDetailService.updateById(lockDetail);
                    System.out.println("解锁" + lockDetail.getSkuId() + "号商品库存");
                }
            }
        }
    }

    /**
     * 当接受到订单服务发送来的订单取消事件，立刻执行解锁库存
     *
     * @param orderTo
     */
    @Transactional
    @Override
    public void orderRelease(OrderTo orderTo) {
        // 不用管订单状态了，直接不用管了。非要再查一遍也行
        R orderBySn = orderFeignService.getOrderBySn(orderTo.getOrderSn());
        if ((int) orderBySn.get("code") == 1 || (int) orderBySn.get("status") == 4) {
            // 直接去查 库存-订单工单
            WareOrderTaskEntity wareOrderTaskEntity =
                    wareOrderTaskService.getOne(
                            new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderTo.getOrderSn()));
            if (wareOrderTaskEntity != null) {
                // 非空，直接去查大工单对应的所有锁定工单(且再进行一次检测，状态得是未解锁)
                List<WareOrderTaskDetailEntity> lockDetailList = wareOrderTaskDetailDao.selectList(
                        new QueryWrapper<WareOrderTaskDetailEntity>().
                                eq("task_id", wareOrderTaskEntity.getId()).
                                eq("lock_status", 1));
                if (lockDetailList != null && lockDetailList.size() > 0) {
                    // 对所有的锁定工单进行解锁
                    for (WareOrderTaskDetailEntity lockDetail : lockDetailList) {
                        baseMapper.stockLockedRelease(lockDetail.getSkuId(), lockDetail.getWareId(), lockDetail.getSkuNum());
                        // 更改工单状态后保存
                        lockDetail.setLockStatus(2);
                        wareOrderTaskDetailService.updateById(lockDetail);
                        System.out.println("解锁" + lockDetail.getSkuId() + "号商品库存");
                    }
                }
            }
        }
    }

    /**
     * 支付成功。则释放被锁定的库存，真正扣减掉该库存
     *
     * @param orderSn 订单SN
     */
    @Override
    @Transactional
    public void handleSuccessPay(String orderSn) {
        WareOrderTaskEntity taskEntity = wareOrderTaskService.getOne(
                new QueryWrapper<WareOrderTaskEntity>().eq("order_sn", orderSn));
        List<WareOrderTaskDetailEntity> detailList = wareOrderTaskDetailService.getBaseMapper().selectList(
                new QueryWrapper<WareOrderTaskDetailEntity>().eq("task_id", taskEntity.getId()));
        for (WareOrderTaskDetailEntity wareOrderTaskDetailEntity : detailList) {
            wareOrderTaskDetailEntity.setLockStatus(3);
            wareOrderTaskDetailService.updateById(wareOrderTaskDetailEntity);
            wareSkuDao.successPayAndDeductStock(
                    wareOrderTaskDetailEntity.getSkuId(),
                    wareOrderTaskDetailEntity.getSkuNum(),
                    wareOrderTaskDetailEntity.getWareId());
        }
    }
}